Day 15 把 CRUD 補完整之後,目前任務已經可以:
新增
查看
編輯
刪除
但還有一個很常用的操作不夠方便。
例如任務目前是:
待處理
開始做之後想改成:
進行中
做完之後再改成:
已完成
Day 14 雖然可以進入編輯頁修改狀態,但如果只是要換一個狀態,每次都進完整編輯頁有點麻煩。
所以 Day 16 的目標是:
直接在首頁快速修改任務狀態。
目前系統只有三種狀態:
todo
→ 待處理
in_progress
→ 進行中
done
→ 已完成
今天要做的是:
首頁選擇新狀態
↓
按「更新」
↓
POST 給 Flask
↓
修改 task.status
↓
寫回 MySQL
↓
回到首頁
今天先不做:
狀態篩選
分類篩選
分類 CRUD
services/
routes/
Claude API
首頁上方原本的:
全部
待處理
進行中
已完成
目前也還只是畫面,今天不處理它。
我先把需求交給 Claude Code:
目前狀態:
- Task CRUD 已完成
- 首頁會從 MySQL 顯示真正的 tasks
- 每筆任務都有 todo / in_progress / done 狀態
- Task Model 已有 STATUS_LABELS 和 STATUS_CSS_CLASSES
- 編輯任務頁面可以修改 status
- 首頁目前只能看到狀態,不能直接修改
今天只做:
「在首頁快速修改任務狀態」
需求:
1. 每張任務卡片增加一個狀態操作區
2. 可以直接選擇:
- 待處理 todo
- 進行中 in_progress
- 已完成 done
3. 使用一般 HTML form + POST
4. 不使用 AJAX
5. 新增一個更新狀態的 Flask route
6. Flask 要根據 task_id 找到 Task
7. 找不到 Task 時回 404
8. 只允許 todo / in_progress / done
9. 更新 task.status 後 db.session.commit()
10. 完成後 redirect 回首頁
11. 原本的狀態 badge 和 task-completed 樣式要繼續正常作用
今天不要:
- 做狀態篩選
- 做分類篩選
- 做分類 CRUD
- 建立 services/
- 建立 routes/
- Claude API
請先規劃會修改哪些檔案,
不要直接修改程式。
最後只需要修改:
app.py
templates/index.html
static/css/style.css
不需要新增資料表,也不用新增新的 HTML 頁面。
Day 16 新增:
@app.route("/tasks/<int:task_id>/status", methods=["POST"])
def update_status(task_id):
task = Task.query.get_or_404(task_id)
new_status = request.form.get("status")
if new_status not in Task.STATUS_LABELS:
abort(400)
task.status = new_status
db.session.commit()
return redirect(url_for("index"))
這條 Route 只負責:
更新 status
不會碰其他欄位。
例如:
task.id = 3
首頁送出:
POST /tasks/3/status
Flask 就會收到:
task_id = 3
接著:
task = Task.query.get_or_404(task_id)
去資料庫找:
id = 3
的任務。
如果找不到,就直接回:
404 Not Found
這跟 Day 14 編輯、Day 15 刪除的做法一致。
首頁下拉選單雖然只會提供:
todo
in_progress
done
但後端還是不能完全相信前端送來的值。
所以我用了:
new_status = request.form.get("status")
if new_status not in Task.STATUS_LABELS:
abort(400)
目前 Task Model 已經有:
STATUS_LABELS = {
"todo": "待處理",
"in_progress": "進行中",
"done": "已完成",
}
所以它的 key 正好就是目前允許的三種狀態。
合法
todo
in_progress
done
如果有人自己送:
abc
就會:
400 Bad Request
而不會把奇怪的值寫進資料庫。
這次是在修改資料庫內容,所以不應該用 GET。
目前專案還是一般:
Flask
+
Jinja2
+
HTML form
沒有 AJAX
所以使用:
methods=["POST"]
最簡單。
流程就是:
選新狀態
↓
按更新
↓
POST
↓
Flask
↓
MySQL
每張任務卡片現在多了一個:
<form
class="status-form"
method="POST"
action="{{ url_for('update_status', task_id=task.id) }}">
<select name="status">
<option value="todo"
{% if task.status == 'todo' %}selected{% endif %}>
待處理
</option>
<option value="in_progress"
{% if task.status == 'in_progress' %}selected{% endif %}>
進行中
</option>
<option value="done"
{% if task.status == 'done' %}selected{% endif %}>
已完成
</option>
</select>
<button class="status-update-button" type="submit">
更新
</button>
</form>
現在首頁就可以直接選擇狀態後,再按「更新」。
例如資料庫目前:
status = in_progress
這段:
{% if task.status == 'in_progress' %}selected{% endif %}
就會讓下拉選單重新整理後顯示:
進行中
所以畫面上的狀態不會每次都回到第一個「待處理」。
Day 13 已經有:
<span class="status-badge {{ task.status_css_class }}">
{{ task.status_label }}
</span>
而 Task Model 有:
STATUS_LABELS = {
"todo": "待處理",
"in_progress": "進行中",
"done": "已完成",
}
以及:
STATUS_CSS_CLASSES = {
"todo": "status-todo",
"in_progress": "status-progress",
"done": "status-done",
}
所以只要資料庫裡:
status
改了,首頁重新讀資料之後,Badge 就會一起更新。
例如:
todo
→ 待處理
in_progress
→ 進行中
done
→ 已完成
CSS 顏色也會跟著變。
首頁本來就有:
<article class="task-card{% if task.status == 'done' %} task-completed{% endif %}">
所以當狀態變成:
done
就會套用:
task-completed
原本的 CSS:
.task-completed h2 {
color: var(--text-secondary);
text-decoration: line-through;
}
會讓任務標題出現劃線。
所以 Day 16 不需要另外重寫這部分。
這次新增:
.status-form {
display: flex;
align-items: center;
gap: 6px;
margin-top: 8px;
}
讓:
下拉選單 + 更新按鈕
排在一起。
下拉選單:
.status-form select {
padding: 5px 8px;
border: 1px solid var(--border);
border-radius: 6px;
font: inherit;
font-size: 0.85rem;
color: var(--text-primary);
background: var(--surface);
}
更新按鈕:
.status-update-button {
padding: 5px 10px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface);
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 600;
}
沒有重新設計整個首頁,只補上這次需要的操作。
<select> 小問題第一次測試時,我打開狀態下拉選單,發現原生 <select> 的選項位置會往左下偏。
問題不是 Flask,也不是狀態表單本身。
原本卡片有:
.task-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(31, 41, 55, 0.08);
}
滑鼠點 <select> 時,本來就會停在卡片上,所以卡片同時進入 hover。
這時:
transform: translateY(-2px);
會干擾 Chrome 系瀏覽器原生 <select> 的下拉選單定位。
所以最後把 transform 拿掉。
原本:
.task-card {
transition: transform 160ms ease, box-shadow 160ms ease;
}
.task-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(31, 41, 55, 0.08);
}
修改成:
.task-card {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 32px;
padding: 22px 24px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface);
box-shadow: 0 4px 16px rgba(31, 41, 55, 0.04);
transition: box-shadow 160ms ease;
}
.task-card:hover {
box-shadow: 0 8px 24px rgba(31, 41, 55, 0.08);
}
現在 hover 還是會有比較明顯的陰影,所以視覺效果還在。
但不再移動整張卡片,也就不會影響 <select> 的位置。
目前首頁已經可以直接看到:
最後回到 MySQL Workbench:
SELECT * FROM tasks;
確認該筆任務的:
status
真的跟著變成:
todo
或:
in_progress
或:
done
這樣才算真的完成:
首頁
↓
POST
↓
Flask
↓
SQLAlchemy
↓
MySQL
整條流程。
現在任務不需要每次進編輯頁才能換狀態。
可以直接從首頁:
待處理
↓
進行中
↓
已完成
而且狀態變更會同步影響:
MySQL status
首頁下拉選單
狀態 Badge
Badge 顏色
完成任務劃線
目前首頁上方:
全部
待處理
進行中
已完成
還沒有真的篩選功能。
Day 17 才要讓這些按鈕開始真的有作用,並加入分類篩選。